import os
import subprocess
import sys
import datetime
from PyQt5.QtWidgets import (
    QMainWindow, QTabWidget, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QTableWidget, QTableWidgetItem, QHeaderView,
    QFileDialog, QMessageBox, QLineEdit, QLabel, QTextEdit,
    QSplitter, QMenu, QAction, QInputDialog, QProgressDialog,
    QGroupBox, QFormLayout, QStatusBar, QComboBox, QCheckBox, QApplication
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QEvent, QTimer, QSettings
from PyQt5.QtGui import QFont, QIcon, QColor, QContextMenuEvent

# 导入其他模块
from config.constants import TASK_EVENT_TYPE, RECORDING_DIR
from utils.helper import logger, check_script_syntax, check_script_dependencies, clear_expired_logs, DEFAULT_LOG_KEEP_DAYS
from db.operations import (
    add_script, get_script_by_id, load_scripts, update_script, delete_script,
    add_task, get_task_by_id, load_tasks, update_task, delete_task,
    load_task_logs, get_task_log_by_id, delete_log_by_id, update_task_log,
    get_script_by_path, get_script_path_by_task_id
)
from core.execution import TaskExecutor, ScheduleManager, TaskEvent
from ui.dialogs import SettingsDialog, TaskDialog, AboutDialog


class RPAManager(QMainWindow):
    """RPA自动化管理工具主窗口"""

    def __init__(self):
        super().__init__()
        self.setWindowTitle("RPA自动化管理工具")
        self.setMinimumSize(1024, 768)  # 最小窗口尺寸
        self.current_executor: dict[int, TaskExecutor] = {}  # 当前运行的任务执行器
        self.init_ui()
        self.init_schedule_manager()
        self.load_all_data()  # 加载初始数据

    def init_ui(self):
        """初始化UI组件"""
        # 1. 创建中心部件和主布局
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        # 2. 创建标签页控件
        self.tabs = QTabWidget()
        self.tabs.setTabPosition(QTabWidget.North)
        main_layout.addWidget(self.tabs)

        # 3. 创建各个标签页
        self.init_scripts_tab()  # 脚本管理标签页
        self.init_tasks_tab()  # 任务管理标签页
        self.init_logs_tab()  # 日志查看标签页
        self.init_dashboard_tab()  # 仪表盘标签页

        # 4. 创建菜单栏
        self.create_menu_bar()

        # 5. 创建状态栏
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.statusBar.showMessage("就绪")

        # 6. 注册自定义事件处理器（处理定时任务触发）
        self.installEventFilter(self)

    def create_menu_bar(self):
        """创建菜单栏"""
        menubar = self.menuBar()

        # 文件菜单
        file_menu = menubar.addMenu("文件")

        # 系统设置
        settings_action = QAction("系统设置", self)
        settings_action.triggered.connect(self.show_settings)
        file_menu.addAction(settings_action)

        file_menu.addSeparator()

        # 退出
        exit_action = QAction("退出", self)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # 帮助菜单
        help_menu = menubar.addMenu("帮助")

        # 关于
        about_action = QAction("关于", self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)

        # 帮助文档
        help_action = QAction("帮助文档", self)
        help_action.triggered.connect(self.show_help)
        help_menu.addAction(help_action)

    def init_schedule_manager(self):
        """初始化定时任务管理器"""
        self.schedule_manager = ScheduleManager()
        # 连接定时任务触发信号
        self.schedule_manager.task_triggered.connect(self.handle_scheduled_task)
        self.schedule_manager.error.connect(lambda msg: self.statusBar.showMessage(f"定时任务错误：{msg}"))
        self.schedule_manager.start()  # 启动定时任务线程

    # -------------------------- 事件过滤器（处理定时任务触发） --------------------------
    def eventFilter(self, obj, event):
        """处理自定义事件（定时任务触发）"""
        if event.type() == TASK_EVENT_TYPE and isinstance(event, TaskEvent):
            # 从事件中获取任务信息（增加类型校验，避免非法事件）
            if hasattr(event, "task_id") and hasattr(event, "script_path"):
                task_id = event.task_id
                script_path = event.script_path
                # 校验脚本路径有效性后执行任务
                if os.path.exists(script_path):
                    self.run_task(task_id)
                else:
                    logger.warning(f"定时任务触发失败：脚本路径不存在（任务ID：{task_id}，路径：{script_path}）")
            return True
        return super().eventFilter(obj, event)

    # -------------------------- 脚本管理标签页 --------------------------
    def init_scripts_tab(self):
        """初始化脚本管理标签页"""
        self.scripts_tab = QWidget()
        layout = QVBoxLayout(self.scripts_tab)

        # 1. 搜索和按钮区域
        top_layout = QHBoxLayout()

        # 搜索框
        self.script_search = QLineEdit()
        self.script_search.setPlaceholderText("搜索脚本名称...")
        self.script_search.returnPressed.connect(self.filter_scripts)
        top_layout.addWidget(self.script_search)

        # 添加脚本按钮
        self.add_script_btn = QPushButton("添加脚本")
        self.add_script_btn.clicked.connect(self.add_new_script)
        top_layout.addWidget(self.add_script_btn)

        # 刷新按钮
        self.refresh_scripts_btn = QPushButton("刷新列表")
        self.refresh_scripts_btn.clicked.connect(self.load_scripts_data)
        top_layout.addWidget(self.refresh_scripts_btn)

        layout.addLayout(top_layout)

        # 2. 脚本表格
        self.scripts_table = QTableWidget()
        self.scripts_table.setColumnCount(6)
        self.scripts_table.setHorizontalHeaderLabels([
            "ID", "名称", "路径", "描述", "创建时间", "最后修改"
        ])
        # 设置表格属性
        self.scripts_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.scripts_table.setEditTriggers(QTableWidget.NoEditTriggers)  # 禁止编辑
        self.scripts_table.setContextMenuPolicy(Qt.CustomContextMenu)  # 启用右键菜单
        self.scripts_table.customContextMenuRequested.connect(self.show_script_context_menu)
        self.scripts_table.cellDoubleClicked.connect(self.edit_script)  # 双击编辑
        layout.addWidget(self.scripts_table)

        # 添加到标签页
        self.tabs.addTab(self.scripts_tab, "脚本管理")

    def load_scripts_data(self):
        """加载脚本数据到表格"""
        try:
            scripts = load_scripts()
            self.scripts_table.setRowCount(len(scripts))

            for row, script in enumerate(scripts):
                # 确保字段存在（避免数据库返回异常数据）
                script_id = script.get('id', '')
                name = script.get('name', '')
                path = script.get('path', '')
                desc = script.get('description', '')
                created_at = script.get('created_at', '')
                last_modified = script.get('last_modified', '')

                self.scripts_table.setItem(row, 0, QTableWidgetItem(str(script_id)))
                self.scripts_table.setItem(row, 1, QTableWidgetItem(name))
                self.scripts_table.setItem(row, 2, QTableWidgetItem(path))
                self.scripts_table.setItem(row, 3, QTableWidgetItem(desc))
                self.scripts_table.setItem(row, 4, QTableWidgetItem(created_at))
                self.scripts_table.setItem(row, 5, QTableWidgetItem(last_modified))

            # 调整列宽（ID列自适应）
            self.scripts_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
            self.statusBar.showMessage(f"已加载 {len(scripts)} 个脚本")
        except Exception as e:
            logger.error(f"加载脚本数据失败：{str(e)}")
            QMessageBox.critical(self, "加载失败", f"脚本数据加载出错：{str(e)}")

    def filter_scripts(self):
        """根据搜索框内容过滤脚本"""
        search_text = self.script_search.text().lower()
        for row in range(self.scripts_table.rowCount()):
            # 确保行内有有效数据
            name_item = self.scripts_table.item(row, 1)
            path_item = self.scripts_table.item(row, 2)
            if not name_item or not path_item:
                self.scripts_table.setRowHidden(row, True)
                continue
            # 匹配名称或路径
            match = (search_text in name_item.text().lower() or
                     search_text in path_item.text().lower())
            self.scripts_table.setRowHidden(row, not match)

    def add_new_script(self):
        """添加新脚本"""
        # 1. 选择Python文件
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择Python脚本", "", "Python Files (*.py);;All Files (*)"
        )

        if not file_path:
            return  # 用户取消选择

        # 2. 检查脚本是否已存在
        try:
            existing = get_script_by_path(file_path)
            if existing:
                QMessageBox.information(
                    self, "已存在",
                    f"该脚本已添加（ID: {existing['id']}，名称: {existing['name']}）"
                )
                return

            # 3. 获取脚本名称（默认使用文件名）
            default_name = os.path.splitext(os.path.basename(file_path))[0]
            name, ok = QInputDialog.getText(
                self, "脚本名称", "请输入脚本名称：", text=default_name
            )

            if not ok or not name.strip():
                return  # 用户取消或名称为空

            # 4. 获取脚本描述
            desc, _ = QInputDialog.getMultiLineText(
                self, "脚本描述", "请输入脚本描述（可选）："
            )

            # 5. 自动检查（根据系统设置）
            settings = QSettings("RPAManager", "Settings")
            check_syntax = settings.value("script/check_syntax", True, type=bool)
            check_deps = settings.value("script/check_deps", True, type=bool)

            syntax_msg = "语法检查已跳过"
            deps_msg = "依赖检查已跳过"

            # 5.1 语法检查
            if check_syntax:
                syntax_ok, syntax_msg = check_script_syntax(file_path)
                if not syntax_ok:
                    reply = QMessageBox.question(
                        self, "语法检查失败",
                        f"{syntax_msg}\n是否仍要添加该脚本？",
                        QMessageBox.Yes | QMessageBox.No
                    )
                    if reply == QMessageBox.No:
                        return

            # 5.2 依赖检查
            if check_deps:
                deps_ok, deps_msg = check_script_dependencies(file_path)
                if not deps_ok:
                    reply = QMessageBox.question(
                        self, "依赖检查失败",
                        f"{deps_msg}\n是否仍要添加该脚本？",
                        QMessageBox.Yes | QMessageBox.No
                    )
                    if reply == QMessageBox.No:
                        return

            # 6. 添加到数据库
            success, script_id = add_script(name.strip(), file_path, desc.strip())
            if success and script_id:
                QMessageBox.information(
                    self, "添加成功",
                    f"脚本添加成功（ID: {script_id}）\n{syntax_msg}\n{deps_msg}"
                )
                self.load_scripts_data()  # 刷新列表
            else:
                QMessageBox.critical(self, "添加失败", "脚本添加失败，请查看日志")
        except Exception as e:
            logger.error(f"添加脚本失败：{str(e)}")
            QMessageBox.critical(self, "添加失败", f"脚本添加出错：{str(e)}")

    def show_script_context_menu(self, position):
        """显示脚本右键菜单"""
        # 获取选中的行（去重）
        selected_rows = {item.row() for item in self.scripts_table.selectedItems()}
        if not selected_rows:
            return  # 没有选中行

        # 创建菜单
        menu = QMenu()
        target_row = list(selected_rows)[0]  # 只处理第一行选中数据

        # 编辑脚本
        edit_action = QAction("编辑脚本信息", self)
        edit_action.triggered.connect(lambda: self.edit_script(target_row))
        menu.addAction(edit_action)

        # 查看脚本
        view_action = QAction("查看脚本内容", self)
        view_action.triggered.connect(lambda: self.view_script(target_row))
        menu.addAction(view_action)

        # 检查脚本
        check_action = QAction("检查脚本（语法+依赖）", self)
        check_action.triggered.connect(lambda: self.check_script(target_row))
        menu.addAction(check_action)

        menu.addSeparator()

        # 删除脚本
        delete_action = QAction("删除脚本", self)
        delete_action.triggered.connect(lambda: self.delete_script(target_row))
        menu.addAction(delete_action)

        # 显示菜单
        menu.exec_(self.scripts_table.mapToGlobal(position))

    def edit_script(self, row):
        """编辑脚本信息（名称和描述）"""
        # 获取脚本ID
        script_id_item = self.scripts_table.item(row, 0)
        if not script_id_item or not script_id_item.text().isdigit():
            QMessageBox.warning(self, "错误", "无效的脚本ID")
            return
        script_id = int(script_id_item.text())

        # 获取当前信息
        try:
            script = get_script_by_id(script_id)
            if not script:
                QMessageBox.warning(self, "错误", f"脚本ID={script_id}不存在")
                return

            # 输入新名称
            name, ok = QInputDialog.getText(
                self, "编辑脚本名称", "请输入新名称：",
                text=script.get('name', '')
            )
            if not ok:
                return

            # 输入新描述
            desc, ok = QInputDialog.getMultiLineText(
                self, "编辑脚本描述", "请输入新描述：",
                text=script.get('description', '')
            )
            if not ok:
                return

            # 更新数据库
            if update_script(script_id, name.strip() or None, desc.strip() or None):
                QMessageBox.information(self, "更新成功", "脚本信息已更新")
                self.load_scripts_data()  # 刷新列表
            else:
                QMessageBox.critical(self, "更新失败", "脚本信息更新失败")
        except Exception as e:
            logger.error(f"编辑脚本失败：{str(e)}")
            QMessageBox.critical(self, "编辑失败", f"脚本编辑出错：{str(e)}")

    def view_script(self, row):
        """查看脚本内容"""
        # 获取脚本路径
        path_item = self.scripts_table.item(row, 2)
        if not path_item:
            QMessageBox.warning(self, "错误", "未获取到脚本路径")
            return
        script_path = path_item.text()

        if not os.path.exists(script_path):
            QMessageBox.warning(self, "文件不存在", f"脚本文件已被删除：{script_path}")
            return

        # 尝试多种编码读取
        encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1']
        content = ""
        encoding_used = "未知"
        for encoding in encodings:
            try:
                with open(script_path, 'r', encoding=encoding) as f:
                    content = f.read()
                encoding_used = encoding
                break
            except UnicodeDecodeError:
                continue
            except Exception as e:
                logger.warning(f"读取脚本文件失败（编码：{encoding}）：{str(e)}")

        if not content:
            QMessageBox.warning(self, "读取失败", "无法读取脚本内容（不支持的编码或文件损坏）")
            return

        # 显示脚本内容
        dialog = QMessageBox(self)
        dialog.setWindowTitle(f"查看脚本：{os.path.basename(script_path)}")
        dialog.setText(f"<small>路径：{script_path}（编码：{encoding_used}）</small>")

        # 添加文本编辑框
        text_edit = QTextEdit()
        text_edit.setPlainText(content)
        text_edit.setReadOnly(True)
        text_edit.setFont(QFont("Consolas", 10))  # 使用等宽字体
        text_edit.setMinimumSize(800, 600)

        dialog.layout().addWidget(text_edit, 1, 0, 1, 2)
        dialog.addButton(QMessageBox.Ok)
        dialog.exec_()

    def check_script(self, row):
        """检查脚本（语法+依赖）"""
        # 获取脚本路径
        path_item = self.scripts_table.item(row, 2)
        if not path_item:
            QMessageBox.warning(self, "错误", "未获取到脚本路径")
            return
        script_path = path_item.text()

        if not os.path.exists(script_path):
            QMessageBox.warning(self, "文件不存在", f"脚本文件已被删除：{script_path}")
            return

        # 执行检查
        try:
            syntax_ok, syntax_msg = check_script_syntax(script_path)
            deps_ok, deps_msg = check_script_dependencies(script_path)

            # 显示结果
            result = f"【语法检查】{syntax_msg}\n\n【依赖检查】{deps_msg}"
            QMessageBox.information(self, "脚本检查结果", result)
        except Exception as e:
            logger.error(f"检查脚本失败：{str(e)}")
            QMessageBox.critical(self, "检查失败", f"脚本检查出错：{str(e)}")

    def delete_script(self, row):
        """删除脚本"""
        # 获取脚本信息
        script_id_item = self.scripts_table.item(row, 0)
        name_item = self.scripts_table.item(row, 1)
        if not script_id_item or not name_item or not script_id_item.text().isdigit():
            QMessageBox.warning(self, "错误", "无效的脚本数据")
            return

        script_id = int(script_id_item.text())
        script_name = name_item.text()

        # 确认删除
        reply = QMessageBox.question(
            self, "确认删除",
            f"确定要删除脚本「{script_name}」吗？\n关联的任务和日志也将被删除！",
            QMessageBox.Yes | QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            try:
                if delete_script(script_id):
                    QMessageBox.information(self, "删除成功", f"脚本「{script_name}」已删除")
                    self.load_scripts_data()  # 刷新列表
                    self.load_tasks_data()  # 同时刷新任务列表
                else:
                    QMessageBox.critical(self, "删除失败", "脚本删除失败，请查看日志")
            except Exception as e:
                logger.error(f"删除脚本失败：{str(e)}")
                QMessageBox.critical(self, "删除失败", f"脚本删除出错：{str(e)}")

    # -------------------------- 任务管理标签页 --------------------------
    def init_tasks_tab(self):
        """初始化任务管理标签页"""
        self.tasks_tab = QWidget()
        layout = QVBoxLayout(self.tasks_tab)

        # 1. 搜索和按钮区域
        top_layout = QHBoxLayout()

        # 搜索框
        self.task_search = QLineEdit()
        self.task_search.setPlaceholderText("搜索任务名称...")
        self.task_search.returnPressed.connect(self.filter_tasks)
        top_layout.addWidget(self.task_search)

        # 筛选框（全部/仅启用）
        self.task_filter = QComboBox()
        self.task_filter.addItems(["全部任务", "仅启用任务"])
        self.task_filter.currentIndexChanged.connect(self.load_tasks_data)
        top_layout.addWidget(self.task_filter)

        # 添加任务按钮
        self.add_task_btn = QPushButton("添加任务")
        self.add_task_btn.clicked.connect(self.add_new_task)
        top_layout.addWidget(self.add_task_btn)

        # 刷新按钮
        self.refresh_tasks_btn = QPushButton("刷新列表")
        self.refresh_tasks_btn.clicked.connect(self.load_tasks_data)
        top_layout.addWidget(self.refresh_tasks_btn)

        layout.addLayout(top_layout)

        # 2. 任务表格
        self.tasks_table = QTableWidget()
        self.tasks_table.setColumnCount(9)
        self.tasks_table.setHorizontalHeaderLabels([
            "ID", "名称", "关联脚本", "Cron表达式", "状态",
            "录屏", "日志级别", "最后运行", "下次运行"
        ])
        # 设置表格属性
        self.tasks_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tasks_table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.tasks_table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tasks_table.customContextMenuRequested.connect(self.show_task_context_menu)
        self.tasks_table.cellDoubleClicked.connect(self.edit_task)
        layout.addWidget(self.tasks_table)

        # 3. 任务执行日志区域
        log_group = QGroupBox("任务执行日志")
        log_layout = QVBoxLayout(log_group)

        # 日志输出区域
        self.task_log_view = QTextEdit()
        self.task_log_view.setReadOnly(True)
        self.task_log_view.setFont(QFont("Consolas", 10))
        log_layout.addWidget(self.task_log_view)

        layout.addWidget(log_group)

        # 添加到标签页
        self.tabs.addTab(self.tasks_tab, "任务管理")

    def load_tasks_data(self):
        """加载任务数据到表格"""
        try:
            # 判断是否只加载启用的任务
            enabled_only = self.task_filter.currentIndex() == 1
            tasks = load_tasks(enabled_only)
            self.tasks_table.setRowCount(len(tasks))

            for row, task in enumerate(tasks):
                # 确保字段存在
                task_id = task.get('id', '')
                name = task.get('name', '')
                script_name = task.get('script_name', '')
                cron_expr = task.get('cron_expression', '')
                is_enabled = task.get('is_enabled', False)
                record_screen = task.get('record_screen', False)
                log_level = task.get('log_level', 'INFO')
                last_run = task.get('last_run', '从未运行')
                next_run = task.get('next_run', '未知')

                # 任务ID
                self.tasks_table.setItem(row, 0, QTableWidgetItem(str(task_id)))
                # 任务名称
                self.tasks_table.setItem(row, 1, QTableWidgetItem(name))
                # 关联脚本
                self.tasks_table.setItem(row, 2, QTableWidgetItem(script_name))
                # Cron表达式
                self.tasks_table.setItem(row, 3, QTableWidgetItem(cron_expr))
                # 状态（启用/禁用，设置颜色）
                status_text = "启用" if is_enabled else "禁用"
                status_item = QTableWidgetItem(status_text)
                status_item.setForeground(QColor("green") if is_enabled else QColor("gray"))
                self.tasks_table.setItem(row, 4, status_item)
                # 录屏
                self.tasks_table.setItem(row, 5, QTableWidgetItem("是" if record_screen else "否"))
                # 日志级别
                self.tasks_table.setItem(row, 6, QTableWidgetItem(log_level))
                # 最后运行
                self.tasks_table.setItem(row, 7, QTableWidgetItem(last_run))
                # 下次运行
                self.tasks_table.setItem(row, 8, QTableWidgetItem(next_run))

            # 调整列宽（ID列自适应）
            self.tasks_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
            self.statusBar.showMessage(f"已加载 {len(tasks)} 个任务")

            # 重新加载定时任务
            self.reload_scheduled_tasks()
        except Exception as e:
            logger.error(f"加载任务数据失败：{str(e)}")
            QMessageBox.critical(self, "加载失败", f"任务数据加载出错：{str(e)}")

    def filter_tasks(self):
        """根据搜索框内容过滤任务"""
        search_text = self.task_search.text().lower()
        for row in range(self.tasks_table.rowCount()):
            # 确保行内有有效数据
            name_item = self.tasks_table.item(row, 1)
            script_item = self.tasks_table.item(row, 2)
            cron_item = self.tasks_table.item(row, 3)
            if not name_item or not script_item or not cron_item:
                self.tasks_table.setRowHidden(row, True)
                continue
            # 匹配名称、脚本或Cron表达式
            match = (search_text in name_item.text().lower() or
                     search_text in script_item.text().lower() or
                     search_text in cron_item.text().lower())
            self.tasks_table.setRowHidden(row, not match)

    def reload_scheduled_tasks(self):
        """重新加载所有定时任务到调度管理器（核心修正：解决Job对象不可调用问题）"""
        if not self.schedule_manager or not self.schedule_manager.isRunning():
            logger.warning("定时任务管理器未启动，跳过任务加载")
            return

        try:
            # 1. 先清除所有现有任务（确保清除逻辑正确，避免残留）
            self.schedule_manager.clear_all_jobs()
            logger.info("已清除所有现有定时任务")

            # 2. 加载所有启用的任务
            enabled_tasks = load_tasks(enabled_only=True)
            if not enabled_tasks:
                self.statusBar.showMessage("没有启用的定时任务")
                return

            added_count = 0
            for task in enabled_tasks:
                # 校验任务核心字段
                task_id = task.get('id')
                cron_expr = task.get('cron_expression')
                if not task_id or not cron_expr:
                    logger.warning(f"跳过无效任务：ID={task_id}，Cron表达式={cron_expr}")
                    continue

                # 获取脚本路径并校验
                script_path = get_script_path_by_task_id(task_id)
                if not script_path or not os.path.exists(script_path):
                    logger.warning(f"任务{task_id}的脚本不存在（路径：{script_path}），跳过添加")
                    continue

                # 3. 调用定时任务管理器添加任务（关键修正：确保返回(success, msg)元组）
                try:
                    # 调用add_job，捕获可能的异常
                    result = self.schedule_manager.add_job(
                        task_id=task_id,
                        script_path=script_path,
                        cron_expr=cron_expr,
                    )
                    # 校验返回值格式
                    if isinstance(result, tuple) and len(result) == 2:
                        success, msg = result
                        if success:
                            added_count += 1
                            logger.info(f"定时任务添加成功：ID={task_id}，Cron={cron_expr}")
                        else:
                            logger.warning(f"任务{task_id}添加失败：{msg}")
                    else:
                        # 若返回Job对象（旧逻辑），手动标记成功
                        if hasattr(result, 'id'):  # 假设Job对象有id属性
                            added_count += 1
                            logger.info(f"定时任务添加成功（兼容模式）：ID={task_id}")
                        else:
                            logger.error(f"任务{task_id}添加失败：返回值格式错误（{type(result)}）")
                except Exception as e:
                    logger.error(f"任务{task_id}添加异常：{str(e)}")

            # 4. 更新状态栏
            self.statusBar.showMessage(f"已加载 {added_count}/{len(enabled_tasks)} 个定时任务")
        except Exception as e:
            logger.error(f"重新加载定时任务失败：{str(e)}")
            self.statusBar.showMessage(f"定时任务加载出错：{str(e)}")

    def add_new_task(self):
        """添加新任务"""
        # 检查是否有脚本
        try:
            scripts = load_scripts()
            if not scripts:
                QMessageBox.warning(self, "无法添加", "请先添加脚本才能创建任务")
                return

            # 显示任务创建对话框
            dialog = TaskDialog(parent=self)
            if dialog.exec_():
                task_data = dialog.get_task_data()
                # 校验任务数据
                required_fields = ['name', 'script_id', 'cron_expression', 'is_enabled', 'record_screen', 'log_level']
                if not all(field in task_data for field in required_fields):
                    QMessageBox.warning(self, "数据错误", "任务数据不完整")
                    return

                # 添加到数据库
                success, task_id = add_task(
                    name=task_data['name'].strip(),
                    script_id=task_data['script_id'],
                    cron_expression=task_data['cron_expression'].strip(),
                    is_enabled=task_data['is_enabled'],
                    record_screen=task_data['record_screen'],
                    log_level=task_data['log_level']
                )
                if success and task_id:
                    QMessageBox.information(self, "添加成功", f"任务添加成功（ID: {task_id}）")
                    self.load_tasks_data()  # 刷新列表
                else:
                    QMessageBox.critical(self, "添加失败", "任务添加失败，请查看日志")
        except Exception as e:
            logger.error(f"添加任务失败：{str(e)}")
            QMessageBox.critical(self, "添加失败", f"任务添加出错：{str(e)}")

    def show_task_context_menu(self, position):
        """显示任务右键菜单"""
        # 获取选中的行（去重）
        selected_rows = {item.row() for item in self.tasks_table.selectedItems()}
        if not selected_rows:
            return

        # 创建菜单
        menu = QMenu()
        target_row = list(selected_rows)[0]

        # 获取任务ID和状态
        task_id_item = self.tasks_table.item(target_row, 0)
        status_item = self.tasks_table.item(target_row, 4)
        if not task_id_item or not status_item or not task_id_item.text().isdigit():
            return

        task_id = int(task_id_item.text())
        status_text = status_item.text()
        is_running = task_id in self.current_executor and self.current_executor[task_id].isRunning()

        # 编辑任务
        edit_action = QAction("编辑任务", self)
        edit_action.triggered.connect(lambda: self.edit_task(target_row))
        menu.addAction(edit_action)

        # 执行任务
        run_action = QAction("立即执行", self)
        run_action.setEnabled(not is_running)
        run_action.triggered.connect(lambda: self.run_task(task_id))
        menu.addAction(run_action)

        # 终止任务
        stop_action = QAction("终止任务", self)
        stop_action.setEnabled(is_running)
        stop_action.triggered.connect(lambda: self.stop_task(task_id))
        menu.addAction(stop_action)

        # 切换状态（启用/禁用）
        toggle_text = "禁用任务" if status_text == "启用" else "启用任务"
        toggle_action = QAction(toggle_text, self)
        toggle_action.triggered.connect(lambda: self.toggle_task_status(task_id, status_text))
        menu.addAction(toggle_action)

        menu.addSeparator()

        # 删除任务
        delete_action = QAction("删除任务", self)
        delete_action.triggered.connect(lambda: self.delete_task(target_row))
        menu.addAction(delete_action)

        # 显示菜单
        menu.exec_(self.tasks_table.mapToGlobal(position))

    def edit_task(self, row):
        """编辑任务"""
        # 获取任务ID
        task_id_item = self.tasks_table.item(row, 0)
        if not task_id_item or not task_id_item.text().isdigit():
            QMessageBox.warning(self, "错误", "无效的任务ID")
            return
        task_id = int(task_id_item.text())

        # 检查任务是否正在运行
        if task_id in self.current_executor and self.current_executor[task_id].isRunning():
            QMessageBox.warning(self, "无法编辑", "任务正在运行，无法编辑")
            return

        # 显示编辑对话框
        try:
            dialog = TaskDialog(task_id=task_id, parent=self)
            if dialog.exec_():
                task_data = dialog.get_task_data()
                # 更新数据库
                success = update_task(
                    task_id=task_id,
                    name=task_data['name'].strip(),
                    cron_expression=task_data['cron_expression'].strip(),
                    is_enabled=task_data['is_enabled'],
                    record_screen=task_data['record_screen'],
                    log_level=task_data['log_level']
                )
                if success:
                    QMessageBox.information(self, "更新成功", "任务信息已更新")
                    self.load_tasks_data()  # 刷新列表
                else:
                    QMessageBox.critical(self, "更新失败", "任务信息更新失败")
        except Exception as e:
            logger.error(f"编辑任务失败：{str(e)}")
            QMessageBox.critical(self, "编辑失败", f"任务编辑出错：{str(e)}")

    def run_task(self, task_id: int):
        """立即执行任务"""
        # 检查任务是否存在
        try:
            task = get_task_by_id(task_id)
            if not task:
                QMessageBox.warning(self, "错误", f"任务ID={task_id}不存在")
                return

            # 检查任务是否已在运行
            if task_id in self.current_executor:
                executor = self.current_executor[task_id]
                if executor.isRunning():
                    QMessageBox.information(self, "提示", f"任务「{task.get('name', '')}」正在运行中")
                    return
                else:
                    # 清理已结束的执行器
                    del self.current_executor[task_id]

            # 获取脚本路径
            script_path = get_script_path_by_task_id(task_id)
            if not script_path or not os.path.exists(script_path):
                QMessageBox.warning(self, "错误", f"脚本文件不存在：{script_path}")
                return

            # 清空当前日志显示
            task_name = task.get('name', f"ID:{task_id}")
            current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            self.task_log_view.clear()
            self.task_log_view.append(f"=== 开始执行任务：{task_name}（ID:{task_id}） ===")
            self.task_log_view.append(f"时间：{current_time}")
            self.task_log_view.append(f"脚本路径：{script_path}")
            self.task_log_view.append(f"录屏：{'是' if task.get('record_screen', False) else '否'}")
            self.task_log_view.append("")

            # 创建并启动任务执行器
            executor = TaskExecutor(
                task_id=task_id,
                record_screen=task.get('record_screen', False)
            )
            # 连接信号
            executor.log_updated.connect(self.task_log_view.append)
            executor.status_updated.connect(
                lambda status: self.statusBar.showMessage(f"任务「{task_name}」状态：{status}")
            )
            executor.finished.connect(self.on_task_finished)
            executor.record_progress.connect(
                lambda sec: self.statusBar.showMessage(
                    f"任务「{task_name}」运行中，已录制 {sec} 秒"
                )
            )

            # 保存执行器引用
            self.current_executor[task_id] = executor
            executor.start()

            # 更新状态栏和标签页
            self.statusBar.showMessage(f"开始执行任务：{task_name}（ID:{task_id}）")
            self.tabs.setCurrentWidget(self.tasks_tab)
        except Exception as e:
            logger.error(f"执行任务{task_id}失败：{str(e)}")
            QMessageBox.critical(self, "执行失败", f"任务执行出错：{str(e)}")

    def stop_task(self, task_id: int):
        """终止正在运行的任务"""
        if task_id not in self.current_executor:
            QMessageBox.warning(self, "错误", "任务未在运行")
            return

        executor = self.current_executor[task_id]
        task = get_task_by_id(task_id)
        task_name = task.get('name', f"ID:{task_id}") if task else f"ID:{task_id}"

        # 确认终止
        reply = QMessageBox.question(
            self, "确认终止",
            f"确定要终止任务「{task_name}」吗？",
            QMessageBox.Yes | QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            try:
                if executor.isRunning():
                    executor.stop()
                    self.statusBar.showMessage(f"任务「{task_name}」已终止")
                # 清理执行器引用
                del self.current_executor[task_id]
            except Exception as e:
                logger.error(f"终止任务{task_id}失败：{str(e)}")
                QMessageBox.critical(self, "终止失败", f"任务终止出错：{str(e)}")

    def on_task_finished(self, task_id: int, status: str, recording_path: str):
        """任务执行完成回调"""
        # 移除执行器引用
        if task_id in self.current_executor:
            del self.current_executor[task_id]

        # 更新任务列表和定时任务
        self.load_tasks_data()

        # 更新状态栏
        task = get_task_by_id(task_id)
        task_name = task.get('name', f"ID:{task_id}") if task else f"ID:{task_id}"
        self.statusBar.showMessage(f"任务「{task_name}」执行完成，状态：{status}")

    def toggle_task_status(self, task_id: int, current_status: str):
        """切换任务状态（启用/禁用）"""
        new_status = current_status == "禁用"  # True=启用，False=禁用
        try:
            task = get_task_by_id(task_id)
            task_name = task.get('name', f"ID:{task_id}") if task else f"ID:{task_id}"

            if update_task(task_id, is_enabled=new_status):
                QMessageBox.information(
                    self, "状态更新",
                    f"任务「{task_name}」已{'启用' if new_status else '禁用'}"
                )
                self.load_tasks_data()  # 刷新列表
            else:
                QMessageBox.critical(self, "更新失败", "任务状态更新失败")
        except Exception as e:
            logger.error(f"切换任务{task_id}状态失败：{str(e)}")
            QMessageBox.critical(self, "更新失败", f"任务状态切换出错：{str(e)}")

    def delete_task(self, row):
        """删除任务"""
        # 获取任务信息
        task_id_item = self.tasks_table.item(row, 0)
        name_item = self.tasks_table.item(row, 1)
        if not task_id_item or not name_item or not task_id_item.text().isdigit():
            QMessageBox.warning(self, "错误", "无效的任务数据")
            return

        task_id = int(task_id_item.text())
        task_name = name_item.text()

        # 检查任务是否正在运行
        if task_id in self.current_executor and self.current_executor[task_id].isRunning():
            QMessageBox.warning(self, "无法删除", "任务正在运行，无法删除")
            return

        # 确认删除
        reply = QMessageBox.question(
            self, "确认删除",
            f"确定要删除任务「{task_name}」吗？\n关联的日志也将被删除！",
            QMessageBox.Yes | QMessageBox.No
        )

        if reply == QMessageBox.Yes:
            try:
                if delete_task(task_id):
                    QMessageBox.information(self, "删除成功", f"任务「{task_name}」已删除")
                    self.load_tasks_data()  # 刷新列表
                    self.load_logs_data()  # 同时刷新日志列表
                else:
                    QMessageBox.critical(self, "删除失败", "任务删除失败，请查看日志")
            except Exception as e:
                logger.error(f"删除任务{task_id}失败：{str(e)}")
                QMessageBox.critical(self, "删除失败", f"任务删除出错：{str(e)}")

    def handle_scheduled_task(self, task_id: int, script_path: str):
        """处理定时任务触发（通过事件机制切换到主线程执行）"""
        # 校验参数有效性
        if not isinstance(task_id, int) or not isinstance(script_path, str):
            logger.warning(f"定时任务参数无效：task_id={task_id}（类型：{type(task_id)}），path={script_path}")
            return

        # 发送自定义事件到主线程（确保事件对象正确）
        try:
            event = TaskEvent(task_id=task_id, script_path=script_path)
            QApplication.postEvent(self, event)
        except Exception as e:
            logger.error(f"发送定时任务事件失败：{str(e)}")
            # 降级处理：直接执行任务
            if os.path.exists(script_path):
                self.run_task(task_id)

    # -------------------------- 日志查看标签页 --------------------------
    def init_logs_tab(self):
        """初始化日志查看标签页"""
        self.logs_tab = QWidget()
        layout = QVBoxLayout(self.logs_tab)

        # 1. 筛选和按钮区域
        top_layout = QHBoxLayout()

        # 任务筛选
        self.log_task_filter = QComboBox()
        self.log_task_filter.addItem("所有任务")  # 索引0表示所有任务
        top_layout.addWidget(self.log_task_filter)

        # 状态筛选
        self.log_status_filter = QComboBox()
        self.log_status_filter.addItems(["所有状态", "运行中", "成功", "失败", "已终止"])
        self.log_status_filter.currentIndexChanged.connect(self.load_logs_data)
        top_layout.addWidget(self.log_status_filter)

        # 刷新按钮
        self.refresh_logs_btn = QPushButton("刷新日志")
        self.refresh_logs_btn.clicked.connect(self.load_logs_data)
        top_layout.addWidget(self.refresh_logs_btn)

        # 清理日志按钮
        self.clear_logs_btn = QPushButton("清理过期日志")
        self.clear_logs_btn.clicked.connect(self.clear_expired_logs)
        top_layout.addWidget(self.clear_logs_btn)

        layout.addLayout(top_layout)

        # 2. 日志表格
        self.logs_table = QTableWidget()
        self.logs_table.setColumnCount(6)
        self.logs_table.setHorizontalHeaderLabels([
            "日志ID", "任务名称", "开始时间", "结束时间", "状态", "录屏"
        ])
        # 设置表格属性
        self.logs_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.logs_table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.logs_table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.logs_table.customContextMenuRequested.connect(self.show_log_context_menu)
        self.logs_table.cellDoubleClicked.connect(self.view_log_details)
        layout.addWidget(self.logs_table)

        # 3. 日志详情区域
        log_detail_group = QGroupBox("日志详情")
        log_detail_layout = QVBoxLayout(log_detail_group)

        self.log_detail_view = QTextEdit()
        self.log_detail_view.setReadOnly(True)
        self.log_detail_view.setFont(QFont("Consolas", 10))
        log_detail_layout.addWidget(self.log_detail_view)

        layout.addWidget(log_detail_group)

        # 添加到标签页
        self.tabs.addTab(self.logs_tab, "日志查看")

    def load_logs_data(self):
        """加载日志数据到表格"""
        try:
            # 1. 刷新任务筛选下拉框
            self.log_task_filter.clear()
            self.log_task_filter.addItem("所有任务")  # 索引0表示所有任务
            tasks = load_tasks()
            task_id_map = {0: None}  # 0: 所有任务
            for task in tasks:
                task_id = task.get('id')
                task_name = task.get('name', f"未知任务（ID:{task_id}）")
                self.log_task_filter.addItem(f"{task_name} (ID:{task_id})")
                task_id_map[self.log_task_filter.count() - 1] = task_id

            # 2. 获取筛选条件
            selected_task_idx = self.log_task_filter.currentIndex()
            task_id = task_id_map.get(selected_task_idx)
            status_filter = self.log_status_filter.currentText()
            status = status_filter if status_filter != "所有状态" else None

            # 3. 加载日志
            logs = load_task_logs(task_id=task_id)
            self.logs_table.setRowCount(len(logs))

            for row, log in enumerate(logs):
                # 只显示符合状态筛选的日志
                log_status = log.get('status', '')
                if status and log_status != status:
                    self.logs_table.setRowHidden(row, True)
                    continue

                self.logs_table.setRowHidden(row, False)  # 确保可见

                # 日志ID
                log_id = log.get('id', '')
                self.logs_table.setItem(row, 0, QTableWidgetItem(str(log_id)))
                # 任务名称
                task_name = log.get('task_name', '未知任务')
                self.logs_table.setItem(row, 1, QTableWidgetItem(task_name))
                # 开始时间
                start_time = log.get('start_time', '')
                self.logs_table.setItem(row, 2, QTableWidgetItem(start_time))
                # 结束时间
                end_time = log.get('end_time', "运行中")
                self.logs_table.setItem(row, 3, QTableWidgetItem(end_time))
                # 状态（根据状态设置颜色）
                status_item = QTableWidgetItem(log_status)
                if log_status == "成功":
                    status_item.setForeground(QColor("green"))
                elif log_status == "失败":
                    status_item.setForeground(QColor("red"))
                elif log_status == "运行中":
                    status_item.setForeground(QColor("blue"))
                else:
                    status_item.setForeground(QColor("gray"))
                self.logs_table.setItem(row, 4, status_item)
                # 录屏
                recording_path = log.get('screen_recording_path', '')
                has_recording = "有" if recording_path and os.path.exists(recording_path) else "无"
                self.logs_table.setItem(row, 5, QTableWidgetItem(has_recording))

            # 调整列宽（ID列自适应）
            self.logs_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
            self.statusBar.showMessage(f"已加载 {len(logs)} 条日志")
        except Exception as e:
            logger.error(f"加载日志数据失败：{str(e)}")
            QMessageBox.critical(self, "加载失败", f"日志数据加载出错：{str(e)}")

    def show_log_context_menu(self, position):
        """显示日志右键菜单"""
        # 获取选中的行（去重）
        selected_rows = {item.row() for item in self.logs_table.selectedItems()}
        if not selected_rows:
            return

        # 创建菜单
        menu = QMenu()
        target_row = list(selected_rows)[0]

        # 获取日志ID和录屏信息
        log_id_item = self.logs_table.item(target_row, 0)
        if not log_id_item or not log_id_item.text().isdigit():
            return
        log_id = int(log_id_item.text())

        # 获取日志详情
        log = get_task_log_by_id(log_id)
        has_recording = False
        if log:
            recording_path = log.get('screen_recording_path', '')
            has_recording = recording_path and os.path.exists(recording_path)

        # 查看详情
        view_action = QAction("查看日志详情", self)
        view_action.triggered.connect(lambda: self.view_log_details(target_row))
        menu.addAction(view_action)

        # 查看录屏（如果有）
        if has_recording:
            record_action = QAction("播放录屏", self)
            record_action.triggered.connect(lambda: self.play_recording(log_id))
            menu.addAction(record_action)

        menu.addSeparator()

        # 删除日志
        delete_action = QAction("删除日志", self)
        delete_action.triggered.connect(lambda: self.delete_log(target_row))
        menu.addAction(delete_action)

        # 显示菜单
        menu.exec_(self.logs_table.mapToGlobal(position))

    def view_log_details(self, row):
        """查看日志详情"""
        # 获取日志ID
        log_id_item = self.logs_table.item(row, 0)
        if not log_id_item or not log_id_item.text().isdigit():
            QMessageBox.warning(self, "错误", "无效的日志ID")
            return
        log_id = int(log_id_item.text())

        # 获取日志详情
        try:
            log = get_task_log_by_id(log_id)
            if not log:
                QMessageBox.warning(self, "错误", f"日志ID={log_id}不存在")
                return

            # 组装详情信息
            task_id = log.get('task_id', '')
            task = get_task_by_id(task_id) if task_id else None
            task_name = task.get('name', f"任务ID:{task_id}") if task else f"任务ID:{task_id}"

            start_time = log.get('start_time', '未知')
            end_time = log.get('end_time', '运行中')
            status = log.get('status', '未知')
            recording_path = log.get('screen_recording_path', '')
            has_recording = "有" if recording_path and os.path.exists(recording_path) else "无"
            log_content = log.get('log_content', '无日志内容')

            details = (f"=== 日志详情 ===\n"
                       f"日志ID：{log_id}\n"
                       f"任务名称：{task_name}\n"
                       f"开始时间：{start_time}\n"
                       f"结束时间：{end_time}\n"
                       f"状态：{status}\n"
                       f"录屏：{has_recording}\n"
                       f"录屏路径：{recording_path if has_recording else '无'}\n\n"
                       f"=== 执行日志 ===\n"
                       f"{log_content}")

            self.log_detail_view.setPlainText(details)
            # 切换到日志标签页
            self.tabs.setCurrentWidget(self.logs_tab)
        except Exception as e:
            logger.error(f"查看日志{log_id}详情失败：{str(e)}")
            QMessageBox.critical(self, "查看失败", f"日志详情加载出错：{str(e)}")

    def play_recording(self, log_id: int):
        """播放录屏文件"""
        try:
            log = get_task_log_by_id(log_id)
            if not log:
                QMessageBox.warning(self, "错误", f"日志ID={log_id}不存在")
                return

            recording_path = log.get('screen_recording_path', '')
            if not recording_path:
                QMessageBox.warning(self, "错误", "没有录屏文件")
                return

            # 修复路径问题：如果是相对路径，转换为绝对路径
            if not os.path.isabs(recording_path):
                # 尝试使用RECORDING_DIR作为基准目录
                from config.constants import RECORDING_DIR
                full_path = os.path.join(RECORDING_DIR, os.path.basename(recording_path))
                if os.path.exists(full_path):
                    recording_path = full_path

            if not os.path.exists(recording_path):
                QMessageBox.warning(self, "错误", f"录屏文件已被删除或路径无效：{recording_path}")
                # 更新日志记录（清除无效路径）
                update_task_log(log_id, screen_recording_path="")
                self.load_logs_data()
                return

            # 使用系统默认播放器打开
            self.statusBar.showMessage(f"正在打开录屏：{os.path.basename(recording_path)}")
            if sys.platform.startswith('win'):
                os.startfile(recording_path)  # Windows
            elif sys.platform.startswith('darwin'):
                subprocess.run(['open', recording_path], check=True)  # macOS
            else:
                subprocess.run(['xdg-open', recording_path], check=True)  # Linux
        except Exception as e:
            logger.error(f"播放录屏{log_id}失败：{str(e)}")
            QMessageBox.critical(self, "播放失败", f"无法打开录屏文件：{str(e)}")

    def delete_log(self, row):
        """删除日志"""
        # 获取日志ID
        log_id_item = self.logs_table.item(row, 0)
        if not log_id_item or not log_id_item.text().isdigit():
            QMessageBox.warning(self, "错误", "无效的日志ID")
            return
        log_id = int(log_id_item.text())

        # 获取日志信息
        try:
            log = get_task_log_by_id(log_id)
            if not log:
                QMessageBox.warning(self, "错误", f"日志ID={log_id}不存在")
                return

            # 确认删除
            reply = QMessageBox.question(
                self, "确认删除",
                f"确定要删除这条日志吗？\n关联的录屏文件也将被删除！",
                QMessageBox.Yes | QMessageBox.No
            )

            if reply == QMessageBox.Yes:
                # 先删除录屏文件（如果存在）
                recording_path = log.get('screen_recording_path', '')
                if recording_path and os.path.exists(recording_path):
                    try:
                        os.remove(recording_path)
                        logger.info(f"删除录屏文件：{recording_path}")
                    except Exception as e:
                        logger.error(f"删除录屏文件{recording_path}失败：{str(e)}")
                        QMessageBox.warning(self, "警告", f"录屏文件删除失败：{str(e)}")

                # 删除日志记录
                if delete_log_by_id(log_id):
                    QMessageBox.information(self, "删除成功", "日志已删除")
                    self.load_logs_data()  # 刷新列表
                else:
                    QMessageBox.critical(self, "删除失败", "日志删除失败")
        except Exception as e:
            logger.error(f"删除日志{log_id}失败：{str(e)}")
            QMessageBox.critical(self, "删除失败", f"日志删除出错：{str(e)}")

    def clear_expired_logs(self):
        """清理过期日志"""
        try:
            # 获取保留天数（从系统设置）
            settings = QSettings("RPAManager", "Settings")
            keep_days = settings.value("logging/keep_days", DEFAULT_LOG_KEEP_DAYS, type=int)

            # 确认清理
            reply = QMessageBox.question(
                self, "确认清理",
                f"确定要清理{keep_days}天前的过期日志和录屏吗？",
                QMessageBox.Yes | QMessageBox.No
            )

            if reply == QMessageBox.Yes:
                # 显示进度对话框
                progress = QProgressDialog("正在清理过期日志...", "取消", 0, 100, self)
                progress.setWindowTitle("清理中")
                progress.setWindowModality(Qt.WindowModal)
                progress.setValue(10)
                QApplication.processEvents()  # 刷新UI

                # 执行清理
                deleted_logs, deleted_recordings = clear_expired_logs(keep_days)
                progress.setValue(100)
                QApplication.processEvents()

                # 显示结果
                msg = (f"清理完成！\n"
                       f"- 删除过期日志：{deleted_logs} 条\n"
                       f"- 删除过期录屏：{deleted_recordings} 个文件")
                QMessageBox.information(self, "清理成功", msg)

                # 刷新日志列表
                self.load_logs_data()
        except Exception as e:
            logger.error(f"清理过期日志失败：{str(e)}")
            QMessageBox.critical(self, "清理失败", f"清理过期日志时出错：{str(e)}")

    # -------------------------- 仪表盘标签页 --------------------------
    def init_dashboard_tab(self):
        """初始化仪表盘标签页（统计信息）"""
        self.dashboard_tab = QWidget()
        layout = QVBoxLayout(self.dashboard_tab)

        # 1. 统计信息卡片
        stats_layout = QHBoxLayout()

        # 脚本数量卡片
        self.scripts_count_card = QGroupBox("脚本总数")
        self.scripts_count_card.setMinimumHeight(100)
        sc_layout = QVBoxLayout(self.scripts_count_card)
        self.scripts_count_label = QLabel("0")
        self.scripts_count_label.setFont(QFont("Arial", 24, QFont.Bold))
        self.scripts_count_label.setAlignment(Qt.AlignCenter)
        sc_layout.addWidget(self.scripts_count_label)
        stats_layout.addWidget(self.scripts_count_card)

        # 任务数量卡片
        self.tasks_count_card = QGroupBox("任务总数")
        self.tasks_count_card.setMinimumHeight(100)
        tc_layout = QVBoxLayout(self.tasks_count_card)
        self.tasks_count_label = QLabel("0")
        self.tasks_count_label.setFont(QFont("Arial", 24, QFont.Bold))
        self.tasks_count_label.setAlignment(Qt.AlignCenter)
        tc_layout.addWidget(self.tasks_count_label)
        stats_layout.addWidget(self.tasks_count_card)

        # 启用任务卡片
        self.enabled_tasks_card = QGroupBox("启用的任务")
        self.enabled_tasks_card.setMinimumHeight(100)
        et_layout = QVBoxLayout(self.enabled_tasks_card)
        self.enabled_tasks_label = QLabel("0")
        self.enabled_tasks_label.setFont(QFont("Arial", 24, QFont.Bold))
        self.enabled_tasks_label.setAlignment(Qt.AlignCenter)
        et_layout.addWidget(self.enabled_tasks_label)
        stats_layout.addWidget(self.enabled_tasks_card)

        # 成功任务卡片
        self.success_tasks_card = QGroupBox("今日成功任务")
        self.success_tasks_card.setMinimumHeight(100)
        st_layout = QVBoxLayout(self.success_tasks_card)
        self.success_tasks_label = QLabel("0")
        self.success_tasks_label.setFont(QFont("Arial", 24, QFont.Bold))
        self.success_tasks_label.setAlignment(Qt.AlignCenter)
        st_layout.addWidget(self.success_tasks_label)
        stats_layout.addWidget(self.success_tasks_card)

        layout.addLayout(stats_layout)

        # 2. 近期任务执行情况
        recent_group = QGroupBox("近期任务执行情况（最近10条）")
        recent_layout = QVBoxLayout(recent_group)

        self.recent_tasks_table = QTableWidget()
        self.recent_tasks_table.setColumnCount(4)
        self.recent_tasks_table.setHorizontalHeaderLabels([
            "任务名称", "开始时间", "状态", "执行时长(秒)"
        ])
        self.recent_tasks_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.recent_tasks_table.setEditTriggers(QTableWidget.NoEditTriggers)
        recent_layout.addWidget(self.recent_tasks_table)

        layout.addWidget(recent_group)

        # 3. 即将执行的任务
        upcoming_group = QGroupBox("即将执行的任务（最近5条）")
        upcoming_layout = QVBoxLayout(upcoming_group)

        self.upcoming_tasks_table = QTableWidget()
        self.upcoming_tasks_table.setColumnCount(3)
        self.upcoming_tasks_table.setHorizontalHeaderLabels([
            "任务名称", "Cron表达式", "下次运行时间"
        ])
        self.upcoming_tasks_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.upcoming_tasks_table.setEditTriggers(QTableWidget.NoEditTriggers)
        upcoming_layout.addWidget(self.upcoming_tasks_table)

        layout.addWidget(upcoming_group)

        # 添加到标签页
        self.tabs.addTab(self.dashboard_tab, "仪表盘")

    def update_dashboard_stats(self):
        """更新仪表盘统计信息"""
        try:
            # 1. 脚本数量
            scripts = load_scripts()
            self.scripts_count_label.setText(str(len(scripts)))

            # 2. 任务数量
            all_tasks = load_tasks()
            self.tasks_count_label.setText(str(len(all_tasks)))

            # 3. 启用的任务
            enabled_tasks = load_tasks(enabled_only=True)
            self.enabled_tasks_label.setText(str(len(enabled_tasks)))

            # 4. 今日成功任务
            today_start = datetime.datetime.now().strftime('%Y-%m-%d 00:00:00')
            today_logs = load_task_logs(start_time_after=today_start)
            success_count = sum(1 for log in today_logs if log.get('status') == '成功')
            self.success_tasks_label.setText(str(success_count))

            # 5. 近期任务执行情况（最近10条）
            self.recent_tasks_table.setRowCount(0)
            recent_logs = load_task_logs()[:10]  # 取最近10条
            self.recent_tasks_table.setRowCount(len(recent_logs))

            for row, log in enumerate(recent_logs):
                task_name = log.get('task_name', '未知任务')
                start_time = log.get('start_time', '未知')
                status = log.get('status', '未知')
                start_dt = None
                end_dt = None

                # 计算执行时长
                try:
                    if log.get('start_time') and log.get('end_time'):
                        start_dt = datetime.datetime.strptime(log['start_time'], '%Y-%m-%d %H:%M:%S')
                        end_dt = datetime.datetime.strptime(log['end_time'], '%Y-%m-%d %H:%M:%S')
                        duration = (end_dt - start_dt).total_seconds()
                        duration_str = f"{duration:.1f}"
                    else:
                        duration_str = "运行中"
                except Exception:
                    duration_str = "未知"

                # 任务名称
                self.recent_tasks_table.setItem(row, 0, QTableWidgetItem(task_name))
                # 开始时间
                self.recent_tasks_table.setItem(row, 1, QTableWidgetItem(start_time))
                # 状态（设置颜色）
                status_item = QTableWidgetItem(status)
                if status == "成功":
                    status_item.setForeground(QColor("green"))
                elif status == "失败":
                    status_item.setForeground(QColor("red"))
                elif status == "运行中":
                    status_item.setForeground(QColor("blue"))
                else:
                    status_item.setForeground(QColor("gray"))
                self.recent_tasks_table.setItem(row, 2, status_item)
                # 执行时长
                self.recent_tasks_table.setItem(row, 3, QTableWidgetItem(duration_str))

            # 6. 即将执行的任务（最近5条）
            self.upcoming_tasks_table.setRowCount(0)
            # 筛选有下次运行时间的启用任务
            upcoming_tasks = [
                task for task in enabled_tasks
                if task.get('next_run') and task.get('next_run') != "未知"
            ]
            # 按下次运行时间排序
            upcoming_tasks.sort(key=lambda x: x.get('next_run', '9999-12-31'))
            # 取最近5条
            upcoming_tasks = upcoming_tasks[:5]
            self.upcoming_tasks_table.setRowCount(len(upcoming_tasks))

            for row, task in enumerate(upcoming_tasks):
                task_name = task.get('name', '未知任务')
                cron_expr = task.get('cron_expression', '未知')
                next_run = task.get('next_run', '未知')

                # 任务名称
                self.upcoming_tasks_table.setItem(row, 0, QTableWidgetItem(task_name))
                # Cron表达式
                self.upcoming_tasks_table.setItem(row, 1, QTableWidgetItem(cron_expr))
                # 下次运行时间
                self.upcoming_tasks_table.setItem(row, 2, QTableWidgetItem(next_run))
        except Exception as e:
            logger.error(f"更新仪表盘统计失败：{str(e)}")
            self.statusBar.showMessage(f"仪表盘更新出错：{str(e)}")

    # -------------------------- 其他功能 --------------------------
    def load_all_data(self):
        """加载所有数据"""
        try:
            self.load_scripts_data()
            self.load_tasks_data()
            self.load_logs_data()
            self.update_dashboard_stats()
        except Exception as e:
            logger.error(f"加载所有数据失败：{str(e)}")
            QMessageBox.critical(self, "初始化失败", f"系统初始化出错：{str(e)}")

    def show_settings(self):
        """显示系统设置对话框"""
        try:
            dialog = SettingsDialog(self)
            if dialog.exec_():
                # 设置更改后重新加载数据（如脚本检查开关、日志保留天数）
                self.load_all_data()
        except Exception as e:
            logger.error(f"显示设置对话框失败：{str(e)}")
            QMessageBox.critical(self, "设置失败", f"打开系统设置出错：{str(e)}")

    def show_about(self):
        """显示关于对话框"""
        try:
            dialog = AboutDialog(self)
            dialog.exec_()
        except Exception as e:
            logger.error(f"显示关于对话框失败：{str(e)}")
            QMessageBox.critical(self, "显示失败", f"打开关于页面出错：{str(e)}")

    def show_help(self):
        """显示帮助文档"""
        QMessageBox.information(
            self, "帮助文档",
            "RPA自动化管理工具使用指南：\n\n"
            "1. 脚本管理：添加和管理Python自动化脚本（支持语法和依赖检查）\n"
            "2. 任务管理：创建定时任务（基于Cron表达式），关联脚本并设置录屏/日志级别\n"
            "3. 日志查看：查看任务执行历史、详细日志，播放录屏，清理过期日志\n"
            "4. 仪表盘：实时查看脚本/任务统计、近期执行记录、即将执行任务\n\n"
            "注意事项：\n"
            "- 执行任务前请确保脚本路径有效，避免文件被移动/删除\n"
            "- 定时任务依赖Cron表达式格式正确（如：0 0 * * * 表示每天凌晨执行）\n"
            "- 录屏文件默认保存在 RECORDING_DIR 目录，建议定期清理以节省空间\n\n"
            "技术支持：如需进一步帮助，请联系开发团队或查阅完整用户手册。"
        )

    def closeEvent(self, event):
        """窗口关闭事件处理（确保所有线程和资源正常释放）"""
        try:
            # 检查是否有任务正在运行
            running_tasks = [
                task_id for task_id, executor in self.current_executor.items()
                if executor and executor.isRunning()
            ]

            if running_tasks:
                reply = QMessageBox.question(
                    self, "确认关闭",
                    f"有 {len(running_tasks)} 个任务正在运行（ID：{','.join(map(str, running_tasks))}），关闭会终止这些任务。\n确定要关闭吗？",
                    QMessageBox.Yes | QMessageBox.No
                )
                if reply == QMessageBox.No:
                    event.ignore()
                    return

                # 终止所有运行中的任务（确保优雅停止，避免资源泄漏）
                for task_id in running_tasks:
                    if task_id in self.current_executor:
                        executor = self.current_executor[task_id]
                        if executor.isRunning():
                            executor.stop()
                            executor.wait()  # 等待线程完全结束
                        del self.current_executor[task_id]

            # 停止定时任务管理器（确保调度线程正常退出）
            if self.schedule_manager and self.schedule_manager.isRunning():
                self.schedule_manager.stop()
                self.schedule_manager.wait()  # 等待调度线程退出

            # 记录关闭日志
            logger.info("RPA自动化管理工具已正常关闭")
            event.accept()
        except Exception as e:
            logger.error(f"窗口关闭过程出错：{str(e)}")
            QMessageBox.warning(self, "关闭警告", f"关闭过程中出现错误：{str(e)}，仍将强制退出")
            event.accept()


# -------------------------- 应用入口 --------------------------
if __name__ == "__main__":
    """启动RPA管理工具主应用"""
    import sys
    from PyQt5.QtWidgets import QApplication

    try:
        # 初始化Qt应用（处理命令行参数）
        app = QApplication(sys.argv)
        # 设置应用图标（可选，需确保图标路径有效）
        # app.setWindowIcon(QIcon("path/to/app_icon.ico"))

        # 创建主窗口并显示
        main_window = RPAManager()
        main_window.show()

        # 启动应用主循环
        sys.exit(app.exec_())
    except Exception as e:
        # 捕获启动阶段的致命错误
        logger.critical(f"应用启动失败：{str(e)}")
        print(f"应用启动失败：{str(e)}")
        sys.exit(1)